var utils = require('./utils')

function Chunk(uploader, file, offset) {
    utils.defineNonEnumerable(this, 'uploader', uploader)
    utils.defineNonEnumerable(this, 'file', file)
    utils.defineNonEnumerable(this, 'bytes', null)
    this.offset = offset
    this.tested = false
    this.retries = 0
    this.pendingRetry = false
    this.preprocessState = 0
    this.readState = 0
    this.loaded = 0
    this.total = 0
    this.chunkSize = this.uploader.opts.chunkSize
    this.startByte = this.offset * this.chunkSize
    this.endByte = this.computeEndByte()
    this.xhr = null
}

var STATUS = Chunk.STATUS = {
    PENDING: 'pending',
    UPLOADING: 'uploading',
    READING: 'reading',
    SUCCESS: 'success',
    ERROR: 'error',
    COMPLETE: 'complete',
    PROGRESS: 'progress',
    RETRY: 'retry'
}

utils.extend(Chunk.prototype, {

    _event: function (evt, args) {
        args = utils.toArray(arguments)
        args.unshift(this)
        this.file._chunkEvent.apply(this.file, args)
    },

    computeEndByte: function () {
        var endByte = Math.min(this.file.size, (this.offset + 1) * this.chunkSize)
        if (this.file.size - endByte < this.chunkSize && !this.uploader.opts.forceChunkSize) {
            // The last chunk will be bigger than the chunk size,
            // but less than 2 * this.chunkSize
            endByte = this.file.size
        }
        return endByte
    },

    getParams: function () {
        console.log(this.file)
        return {
            chunkNumber: this.offset + 1,
            chunkSize: this.uploader.opts.chunkSize,
            currentChunkSize: this.endByte - this.startByte,
            totalSize: this.file.size,
            identifier: this.file.uniqueIdentifier,
            filename: this.file.name,
            relativePath: this.file.relativePath,
            totalChunks: this.file.chunks.length,
            md5: this.file.md5,
            guid: this.file.guid,
            signature: this.file.signature,
        }
    },

    getTarget: function (target, params) {
        if (!params.length) {
            return target
        }
        if (target.indexOf('?') < 0) {
            target += '?'
        } else {
            target += '&'
        }
        return target + params.join('&')
    },

    test: function () {
        var $ = this
        if(!$.uploader.opts.chunkEnable){
            $.tested = true
            $.send();
        }else{
            this.xhr = new XMLHttpRequest()
            this.xhr.addEventListener('load', testHandler, false)
            this.xhr.addEventListener('error', testHandler, false)
            var testMethod = utils.evalOpts(this.uploader.opts.testMethod, this.file, this)
            var data = this.prepareXhrRequest(testMethod, true)
            this.xhr.send(data)
            function testHandler(event) {
                var status = $.status(true)
                if (status === STATUS.ERROR) {
                    $._event(status, $.message())
                    $.uploader.uploadNextChunk()
                } else if (status === STATUS.SUCCESS) {
                    $._event(status, $.message())
                    $.tested = true
                } else if (!$.file.paused) {
                    // Error might be caused by file pause method
                    // Chunks does not exist on the server side
                    $.tested = true
                    $.send()
                }
            }
        }
    },

    preprocessFinished: function () {
        // Compute the endByte after the preprocess function to allow an
        // implementer of preprocess to set the fileObj size
        this.endByte = this.computeEndByte()
        this.preprocessState = 2
        this.send()
    },

    readFinished: function (bytes) {
        this.readState = 2
        this.bytes = bytes
        this.send()
    },

    send: function () {
        var preprocess = this.uploader.opts.preprocess
        var read = this.uploader.opts.readFileFn
        if (utils.isFunction(preprocess)) {
            switch (this.preprocessState) {
                case 0:
                    this.preprocessState = 1
                    preprocess(this)
                    return
                case 1:
                    return
            }
        }
        switch (this.readState) {
            case 0:
                this.readState = 1
                read(this.file, this.file.fileType, this.startByte, this.endByte, this)
                return
            case 1:
                return
        }
        if (this.uploader.opts.testChunks && !this.tested) {
            this.test()
            return
        }

        this.loaded = 0
        this.total = 0
        this.pendingRetry = false

        // Set up request and listen for event
        this.xhr = new XMLHttpRequest()
        this.xhr.upload.addEventListener('progress', progressHandler, false)
        this.xhr.addEventListener('load', doneHandler, false)
        this.xhr.addEventListener('error', doneHandler, false)

        var uploadMethod = utils.evalOpts(this.uploader.opts.uploadMethod, this.file, this)
        var data = this.prepareXhrRequest(uploadMethod, false, this.uploader.opts.method, this.bytes)
        this.xhr.send(data)

        var $ = this

        function progressHandler(event) {
            if (event.lengthComputable) {
                $.loaded = event.loaded
                $.total = event.total
            }
            $._event(STATUS.PROGRESS, event)
        }

        function doneHandler(event) {
            var msg = $.message()
            $.processingResponse = true
            $.uploader.opts.processResponse(msg, function (err, res) {
                $.processingResponse = false
                if (!$.xhr) {
                    return
                }
                $.processedState = {
                    err: err,
                    res: res
                }
                var status = $.status()
                if (status === STATUS.SUCCESS || status === STATUS.ERROR) {
                    delete this.data
                    $._event(status, res)
                    status === STATUS.ERROR && $.uploader.uploadNextChunk()
                } else {
                    $._event(STATUS.RETRY, res)
                    $.pendingRetry = true
                    $.abort()
                    $.retries++
                    var retryInterval = $.uploader.opts.chunkRetryInterval
                    if (retryInterval !== null) {
                        setTimeout(function () {
                            $.send()
                        }, retryInterval)
                    } else {
                        $.send()
                    }
                }
            }, $.file, $)
        }
    },

    abort: function () {
        var xhr = this.xhr
        this.xhr = null
        this.processingResponse = false
        this.processedState = null
        if (xhr) {
            xhr.abort()
        }
    },

    status: function (isTest) {
        if (this.readState === 1) {
            return STATUS.READING
        } else if (this.pendingRetry || this.preprocessState === 1) {
            // if pending retry then that's effectively the same as actively uploading,
            // there might just be a slight delay before the retry starts
            return STATUS.UPLOADING
        } else if (!this.xhr) {
            return STATUS.PENDING
        } else if (this.xhr.readyState < 4 || this.processingResponse) {
            // Status is really 'OPENED', 'HEADERS_RECEIVED'
            // or 'LOADING' - meaning that stuff is happening
            return STATUS.UPLOADING
        } else {
            var _status
            if (this.uploader.opts.successStatuses.indexOf(this.xhr.status) > -1) {
                // HTTP 200, perfect
                // HTTP 202 Accepted - The request has been accepted for processing, but the processing has not been completed.
                _status = STATUS.SUCCESS
            } else if (this.uploader.opts.permanentErrors.indexOf(this.xhr.status) > -1 ||
                !isTest && this.retries >= this.uploader.opts.maxChunkRetries) {
                // HTTP 415/500/501, permanent error
                _status = STATUS.ERROR
            } else {
                // this should never happen, but we'll reset and queue a retry
                // a likely case for this would be 503 service unavailable
                this.abort()
                _status = STATUS.PENDING
            }
            var processedState = this.processedState
            if (processedState && processedState.err) {
                _status = STATUS.ERROR
            }
            return _status
        }
    },

    message: function () {
        return this.xhr ? this.xhr.responseText : ''
    },

    progress: function () {
        if (this.pendingRetry) {
            return 0
        }
        var s = this.status()
        if (s === STATUS.SUCCESS || s === STATUS.ERROR) {
            return 1
        } else if (s === STATUS.PENDING) {
            return 0
        } else {
            return this.total > 0 ? this.loaded / this.total : 0
        }
    },

    sizeUploaded: function () {
        var size = this.endByte - this.startByte
        // can't return only chunk.loaded value, because it is bigger than chunk size
        if (this.status() !== STATUS.SUCCESS) {
            size = this.progress() * size
        }
        return size
    },

    prepareXhrRequest: function (method, isTest, paramsMethod, blob) {
        // Add data from the query options
        var query = utils.evalOpts(this.uploader.opts.query, this.file, this, isTest)
        query = utils.extend(this.getParams(), query)

        // processParams
        query = this.uploader.opts.processParams(query, this.file, this, isTest)
        var target =utils.evalOpts(this.uploader.opts.target, this.file, this, isTest);
        var data = null
        if (method === 'GET' || paramsMethod === 'octet') {
            // Add data from the query options
            var params = []
            utils.each(query, function (v, k) {
                params.push([encodeURIComponent(k), encodeURIComponent(v)].join('='))
            })
            target = this.getTarget(target, params)
            data = blob || null
        } else {
            // Add data from the query options
            data = new FormData()
            utils.each(query, function (v, k) {
                data.append(k, v)
            })
            if (typeof blob !== 'undefined') {
                data.append(this.uploader.opts.fileParameterName, blob, this.file.name)
            }
        }

        this.xhr.open(method, target, true)
        this.xhr.withCredentials = this.uploader.opts.withCredentials

        // Add data from header options
        utils.each(utils.evalOpts(this.uploader.opts.headers, this.file, this, isTest), function (v, k) {
            this.xhr.setRequestHeader(k, v)
        }, this)

        return data
    }

})

module.exports = Chunk
